<?php
require_once 'Rfc3986.php';
require_once 'OAuthStore.php';
require_once 'OAuthRequest.php';
require_once 'OAuthException.php';
require_once 'OAuthSignatureVerifier.php';

/**
 * Class OAuthRequestVerifier
 * Edit from original library OAuth-PHP of Marc Worrell <marcw@pobox.com>
 *
 * Using data store to verify the request
 *
 * @editor: Phong Linh <langxangvn@gmail.com>
 * @see http://code.google.com/p/oauth-php
*/
class OAuthRequestVerifier extends OAuthRequest {

	/**
	 * Invalid timestamp code
	 *
	 */
	public static $INVALID_TIMESTAMP = 92;
	
	/**
	* Invalid nonce code
	*
	*/
	public static $INVALID_NONCE = 93;
	
	/**
	 * Missing API key
	 *
	 */
	public static $MISSING_CONSUMER_KEY = 94;
	
	/**
	* Missing API method
	*
	*/
	public static $MISSING_METHOD = 95;
	
	/**
	* Invalid signature
	*
	*/
	public static $INVALID_SIGNATURE = 96;
	
	/**
	* Missing signature
	*
	*/
	public static $MISSING_SIGNATURE = 97;
	
	/**
	* Failed when authenticate
	*
	*/
	public static $AUTHENTICATE_FAILED = 98;
	
	/**
	* Not authorized to action
	*
	*/
	public static $NOT_AUTHORIZED = 99;	
	
	/**
	* Invalid API token
	*
	*/
	public static $INVALID_TOKEN = 100;	
	
	/**
	* Service not available
	*
	*/
	public static $SERVICE_UNAVAILABLE = 105;
	
	/**
	* Request format not supported
	*
	*/
	public static $UNSUPPORTED_RESPONSE_FORMAT = 111;
	
	/**
	* Request method not supported
	*
	*/
	public static $UNSUPPORTED_METHOD = 112;	
	
	/**
	* Invalid SOAP
	*
	*/
	public static $INVALID_SOAP = 114;	
	
	/**
	* Invalid XML-RPC
	*
	*/
	public static $INVALID_XML_RPC = 115;
	
	/**
	* Bad URL
	*
	*/
	public static $BAD_URL = 116;
	
	/**
	* Unknown error
	*
	*/
	public static $UNKNOWN_ERROR = 117;
	
	/**
	 * Array contain definition or errors
	 *
	 */
	protected $_ERRORS = array(
		92 => array(
			'name' => 'Invalid timestamp', 
			'msg' => 'The passed timestamp was invalid.'),			
		93 => array(
			'name' => 'Invalid nonce', 
			'msg' => 'Provided nonce has been seen before.'),			
		94 => array(
			'name' => 'Missing API key', 
			'msg' => 'The call required API key but no API key was sent.'),			
		95 => array(
			'name' => 'Missing method', 
			'msg' => 'The call required method but no method was sent.'),		
		96 => array(
			'name' => 'Invalid signature', 
			'msg' => 'The passed signature was invalid.'),
		97 => array(
			'name' => 'Missing signature', 
			'msg' => 'The call required signing but no signature was sent.'),
		98 => array(
			'name' => 'Login failed / Invalid auth token', 
			'msg' => 'The login details or auth token passed were invalid.'),
		99 => array(
			'name' => 'User not logged in / Insufficient permissions',
			'msg' => 'The method requires user authentication but the user was not logged in, or the authenticated method call did not have the required permissions.'),
		100 => array(
			'name' => 'Invalid API key/token', 
			'msg' => 'The API key/token passed was not valid or has expired.'),
		105 => array(
			'name' => 'Service currently unavailable', 
			'msg' => 'The requested service is temporarily unavailable.'),
		111 => array(
			'name' => 'Format "xxx" not found', 
			'msg' => 'The requested response format was not found.'),
		112 => array(
			'name' => 'Method "xxx" not found', 
			'msg' => 'The requested method was not found.'),						
		114 => array(
			'name' => 'Invalid SOAP envelope', 
			'msg' => 'The SOAP envelope send in the request could not be parsed.'),	
		115 => array(
			'name' => 'Invalid XML-RPC Method Call', 
			'msg' => 'The XML-RPC request document could not be parsed.'),			
		116 => array(
			'name' => 'Bad URL found', 
			'msg' => 'One or more arguments contained a URL that has been used for abuse on site.'),									
		);		
	
	/**
	 * The data store
	 *
	 * @var OAuthStore 
	 *
	 */
	protected $_store;
	
	/**
	 * Construct the request to be verified
	 * 
	 * @param string $uri
	 * @param string $method	// default value is null, fixed can not read request body 2012/03/27
	 * @param array params The request parameters
	 */
	public function __construct($uri = null, $method = null, $params = null) {	
		if ($params) {
			$encodedParams = array();
			foreach ($params as $k => $v) {
				if (preg_match("/^oauth_/", $k)) {  
					continue;
				}
				$encodedParams[rawurlencode($k)] = rawurlencode($v);
			}
						
			$this->_param = array_merge($this->_param, $encodedParams);
		}
				
		parent::__construct($uri, $method);
		$this->_store = OAuthStore::instance();	// default store
		
	}
	
	/**
	 * Get definition of specific error
	 *
	 * @param string $code The error code
	 * @return array The error definition
	 *
	 */
	public function getError($code) {
		if(array_key_exists($code, $this->_ERRORS)) {
			return $this->_ERRORS[$code];
		}
		
		throw new Exception('Not defined error.');
	}
	
	
	/**
	 * Get message of specific error
	 *
	 * @param string $code The error code
	 * @return array The error message
	 *
	 */
	public function getErrorMessage($code) {
		try {
			$error = $this->getError($code);
			return $error['msg'];	
		} catch(Exception $e) {
			throw new Exception('Can\'t get error message associte to code ' . $code . '.');
		}
	}
	
	/**
	* See if the current request is signed
	* 
	* @return boolean
	*/
	public function isRequestSigned() {
		if (isset($_REQUEST[self::$OAUTH_SIGNATURE])) {
			$signed = true;
		} else {	// in headers
			$headers = $this->getAllHeaders();
			if (isset($headers['Authorization']) && 
				strpos($headers['Authorization'], self::$OAUTH_SIGNATURE) !== false)
			{
				$signed = true;
			} else {
				$signed = false;
			}
		}
		
		return $signed;
	}
	
	/**
	 * Verify the request if it seemed to be signed.
	 * 
	 * @param string type the kind of token needed, defaults to 'access'
	 * @exception OAuthException thrown when the request did not verify
	 * @return boolean      true when signed, false when not signed
	 */
	public function verifyIfSigned ($tokenType = 'access') {
		if ($this->getParam(self::$OAUTH_CONSUMER_KEY)) {
			$this->verify($tokenType);
			$signed = true;
		} else {
			$signed = false;
		}
		return $signed;
	}	
	
	/**
	 * Verify the request
	 * 
	 * @param string $tokenType The kind of token needed, defaults to 'access' (false, 'access', 'request')
	 * @param boolean $bypassNonce Indicate to bypass check nonce
	 * @exception ApiServerException thrown when the request did not verify
	 * @return array(email, userId) associated with token (false when no user associated)
	 */
	public function verify($tokenType = 'access', $bypassNonce = false) {
		$consumerKey	= $this->getParam(self::$OAUTH_CONSUMER_KEY);
		$token			= $this->getParam(self::$OAUTH_TOKEN);
		
		// not authorized yet
		$userId		= false;
		$username	= false;
		$registryId	= false;
		$email		= false;
		$secrets	= array();
		
		if (!$consumerKey) {
			$code = self::$MISSING_CONSUMER_KEY;
			$error = $this->getErrorMessage($code);
			throw new OAuthException($error, $code);			
		}
		
		if (!($tokenType === false || $token)) {
			$code = self::$AUTHENTICATE_FAILED;
			$error = $this->getErrorMessage($code);
			throw new OAuthException($error, $code);
		}
		
		try {
			$secrets = $this->_store->getSecretsForVerify(
				Rfc3986::urlDecode($consumerKey), 
				Rfc3986::urlDecode($token), $tokenType);
		} catch (Exception $e) {
			$code = self::$INVALID_TOKEN;
			$error = $this->getErrorMessage($code);
			throw new OAuthException($error, $code);	
		}
		
		if(!$bypassNonce) { // not is an authorize request (authorize request through web just contain frob)
			try {	// check timestamp
				
				$this->_store->verifyTimestamp($this->getParam(self::$OAUTH_TIMESTAMP, true));
			} catch(Exception $e) {
				$code = self::$INVALID_TIMESTAMP;
				$error = $this->getErrorMessage($code);
				throw new OAuthException($error, $code);
			}
			
			try {					// check token and timestamp
				$this->_store->verifyNonce(
					Rfc3986::urlDecode($consumerKey),
					Rfc3986::urlDecode($token),
					$this->getParam(self::$OAUTH_TIMESTAMP, true),		// get timestamp and decode
					$this->getParam(self::$OAUTH_NONCE, true));			// get nonce and decode						
			} catch(Exception $e) {
				$code = self::$INVALID_NONCE;
				$error = $this->getErrorMessage($code);
				throw new OAuthException($error, $code);
			}
		}
		
		$signature = $this->getParam(self::$OAUTH_SIGNATURE);			// get encoded signature
		if (empty($signature)) {
			$code = self::$MISSING_SIGNATURE;
			$error = $this->getErrorMessage($code);
			throw new OAuthException($error, $code);	
		} 
		
		try {
			$this->verifySignature($secrets['consumer_secret'], $secrets['token_secret'], $tokenType);
		} catch (Exception $e) {
			$code = self::$INVALID_SIGNATURE;
			$error = $this->getErrorMessage($code);
			throw new OAuthException($error, $code);	
		}
		
		// check the optional body signature
		if ($this->getParam(self::$XOAUTH_BODY_SIGNATURE)) {
			$method = $this->getParam(self::$XOAUTH_BODY_SIGNATURE_METHOD);
			if (empty($method))	{
				$method = $this->getParam(self::$OAUTH_SIGNATURE_METHOD);
			}

			try	{
				$this->verifyDataSignature($this->getBody(), 
					$secrets['consumer_secret'], $secrets['token_secret'], 
					$method, $this->getParam(self::$XOAUTH_BODY_SIGNATURE));
			} catch (Exception $e) {
				throw new OAuthException('Verification of body signature failed.');
			}
		}
		
		// All OK - fetch the user associated with this request
		if (isset($secrets['user_id']) && isset($secrets['registry_id'])) {
			$userId = $secrets['user_id'];
			$registryId = $secrets['registry_id'];
		}
		
		// check if the consumer wants us to reset the ttl of this token
		$ttl = $this->getParam(self::$XOAUTH_TOKEN_TTL, true);
		if (is_numeric($ttl)) {
			$this->_store->setAccessTokenTtl(Rfc3986::urlDecode($token), $ttl);
		}
		
		return array('user_id' => $userId, 'consumer_key' => $consumerKey, 
			'registry_id' => $registryId); 
	}	
	
	/**
	 * Common function to calculate the signature of the request. The signature is 
	 * returned encoded in the form as used in the URL. Verify the signature of the request, 
	 * using the method in oauth_signature_method.
	 * 
	 * @param string $consumerSecret application API key
	 * @param string $tokenSecret
	 * @exception OAuthException thrown when the signature method is unknown 
	 * @exception OAuthException when not all parts available
	 * @exception OAuthException when signature does not match
	 * @return string
	 */
	protected function verifySignature($consumerSecret, $tokenSecret, $tokenType = 'access') {	
		/*
			'oauth_consumer_key',
			'oauth_signature_method',
			'oauth_signature'	
			'oauth_timestamp',
			'oauth_nonce',	
		*/
		$required = array(
			self::$OAUTH_CONSUMER_KEY,
			self::$OAUTH_SIGNATURE_METHOD,
			self::$OAUTH_SIGNATURE,
			self::$OAUTH_TIMESTAMP,
			self::$OAUTH_NONCE
		);
		
		if($tokenType !== false) {
			$required[] = self::$OAUTH_TOKEN;
		}
		
		foreach ($required as $req) {
			if (!isset($this->_params[$req]))	{
				throw new OAuthException('Can\'t sign request, missing parameter "' . $req . '"');
			}
		}		
		
		$signature = $this->_params[self::$OAUTH_SIGNATURE];	// request encoded signature
		
		// version 1.0 check
		$this->checksVersion();
		
		$signatureBase = $this->getSignatureBase();
		$signatureMethod = $this->_params[self::$OAUTH_SIGNATURE_METHOD];
		return $this->verifyDataSignature($signatureBase, $consumerSecret, 
			$tokenSecret, $signature, $signatureMethod);
	}
	
	/**
	 * Verify the signature of a string.
	 * 
	 * @param string        data
	 * @param string        consumerSecret
	 * @param string        tokenSecret
	 * @param string        signature
	 * @param string        signatureMethod
	 * @exception OAuthException thrown when the signature method is unknown 
	 * @exception OAuthException when signature does not match
	 */
	public function verifyDataSignature($data, $consumerSecret, $tokenSecret, 
		$signature, $signatureMethod = 'HMAC-SHA1')
	{
		if (is_null($data)) {
			$data = '';
		}	
		
		return $signature;		

		$sig = $this->getSignatureVerifier($signatureMethod);
		if (!$sig->verify($data, $consumerSecret, $tokenSecret, $signature)) {
			throw new OAuthException('Verification of signature failed (signature base string was "' . $data . '") . ');
		}
		
		return $signature;
	}
}

?>